Skip to content

ConfigDrive: write every SSH key as its own entry in the OpenStack metadata - #14164

Open
wido wants to merge 1 commit into
apache:mainfrom
wido:configdrive-multiple-ssh-keys
Open

wido wants to merge 1 commit into
apache:mainfrom
wido:configdrive-multiple-ssh-keys

Conversation

@wido

@wido wido commented Sep 14, 2026

Copy link
Copy Markdown
Contributor

Description

An Instance with more than one SSH keypair receives its keys as one newline-joined string. The OpenStack meta_data.json builder wrote that string as a single key: keys held one object and public_keys one entry whose value contained both keys with a newline in between. cloud-init treats every public_keys value as one key and never splits it, so only the first key worked at best. Present since multiple SSH keys were introduced in 4.17 (#5965).

The builder now emits one keys object and one public_keys entry per key. A key is named after its comment when it has one that no earlier key used, otherwise key for a single key and key0, key1, ... for several.

Output for an Instance with two keys, the second one with a comment:

{
  "keys": [
    {"type": "ssh", "data": "ssh-ed25519 AAAA...bsP2", "name": "key0"},
    {"type": "ssh", "data": "ssh-ed25519 AAAA...LOn user@laptop", "name": "user@laptop"}
  ],
  "public_keys": {
    "key0": "ssh-ed25519 AAAA...bsP2",
    "user@laptop": "ssh-ed25519 AAAA...LOn user@laptop"
  }
}

Types of changes

  • Bug fix (non-breaking change which fixes an issue)

Feature/Enhancement Scale or Bug Severity

  • Major

How Has This Been Tested?

Unit tests added to ConfigDriveBuilderTest for a single key, a key with a comment, two keys, duplicate comments with blank lines, and untouched other metadata. Observed on a KVM Instance with two keypairs: meta_data.json on the ConfigDrive had both keys in one string and cloud-init installed neither correctly.

@codecov

codecov Bot commented Sep 14, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 19.89%. Comparing base (dc0ba1c) to head (a08f069).

Additional details and impacted files
@@             Coverage Diff              @@
##               main   #14164      +/-   ##
============================================
- Coverage     19.89%   19.89%   -0.01%     
- Complexity    20144    20149       +5     
============================================
  Files          6371     6371              
  Lines        576829   576838       +9     
  Branches      70627    70629       +2     
============================================
- Hits         114778   114769       -9     
- Misses       449507   449524      +17     
- Partials      12544    12545       +1     
Flag Coverage Δ
uitests 3.71% <ø> (ø)
unittests 21.16% <100.00%> (-0.01%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@wido

wido commented Sep 14, 2026

Copy link
Copy Markdown
Contributor Author

I came accross this while testing PR #13758

The authorized_keys file was wrong:

root@VM-c38cdf78-5132-4e69-bd30-0a246165447e:~# cat .ssh/authorized_keys
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFeMnwadvS7Z/sN0yCVnfcMgvxrmlNr2zElAMlFvbsP2 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEPnHzS1LN+3VoXrUBWhRlIbWlSiqVdPynRNDy4bnLOn
root@VM-c38cdf78-5132-4e69-bd30-0a246165447e:~#

The newline between the SSH keys wasn't correct. After this commit it now works as expected.

root@VM-baf04c61-ddbc-456b-b0bd-7bfdaee8d896:~# cat /root/.ssh/authorized_keys
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEPnHzS1LN+3VoXrUBWhRlIbWlSiqVdPynRNDy4bnLOn
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFeMnwadvS7Z/sN0yCVnfcMgvxrmlNr2zElAMlFvbsP2
root@VM-baf04c61-ddbc-456b-b0bd-7bfdaee8d896:~#

@wido wido added this to the 24.0 milestone Sep 14, 2026
@wido

wido commented Sep 14, 2026

Copy link
Copy Markdown
Contributor Author

To add, this is how meta_data.json looked in the incorrect format and the correct format:

{
  "availability_zone": "AMS02",
  "hostname": "VM-c38cdf78-5132-4e69-bd30-0a246165447e",
  "uuid": "c38cdf78-5132-4e69-bd30-0a246165447e",
  "keys": [
    {
      "type": "ssh",
      "data": "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFeMnwadvS7Z/sN0yCVnfcMgvxrmlNr2zElAMlFvbsP2\nssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEPnHzS1LN+3VoXrUBWhRlIbWlSiqVdPynRNDy4bnLOn",
      "name": "key"
    }
  ],
  "public_keys": {
    "key": "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFeMnwadvS7Z/sN0yCVnfcMgvxrmlNr2zElAMlFvbsP2\nssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEPnHzS1LN+3VoXrUBWhRlIbWlSiqVdPynRNDy4bnLOn"
  }
}
{
  "availability_zone": "AMS02",
  "hostname": "VM-baf04c61-ddbc-456b-b0bd-7bfdaee8d896",
  "uuid": "baf04c61-ddbc-456b-b0bd-7bfdaee8d896",
  "keys": [
    {
      "type": "ssh",
      "data": "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFeMnwadvS7Z/sN0yCVnfcMgvxrmlNr2zElAMlFvbsP2",
      "name": "key0"
    },
    {
      "type": "ssh",
      "data": "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEPnHzS1LN+3VoXrUBWhRlIbWlSiqVdPynRNDy4bnLOn",
      "name": "key1"
    }
  ],
  "public_keys": {
    "key0": "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFeMnwadvS7Z/sN0yCVnfcMgvxrmlNr2zElAMlFvbsP2",
    "key1": "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEPnHzS1LN+3VoXrUBWhRlIbWlSiqVdPynRNDy4bnLOn"
  }
}

This PR has been tested in real-life with a Ubuntu 26.04 VM using ConfigDrive as it's cloud-init datasource.

root@VM-baf04c61-ddbc-456b-b0bd-7bfdaee8d896:~# cat /etc/cloud/cloud.cfg.d/99-cloudstack.cfg 
# cloud-init settings for Apache CloudStack guests.
#
# Installed as /etc/cloud/cloud.cfg.d/99-cloudstack.cfg so that the
# distribution's own /etc/cloud/cloud.cfg (system_info.distro, paths, package
# handling) stays in place and only the keys below are overridden.

# ConfigDrive is tried first: CloudStack attaches it as an ISO labeled
# 'config-2' and cloud-init reads it in the local stage, before networking.
# The CloudStack datasource (metadata served by the virtual router) is the
# fallback for networks without ConfigDrive.
datasource_list: [ConfigDrive, CloudStack, None]

…tadata

An Instance with more than one SSH keypair gets its keys as a single
newline-joined string. The OpenStack meta_data.json builder passed that
string through as one key, so "keys" held a single object and
"public_keys" a single map entry whose value contained both keys with a
newline in between. cloud-init treats every public_keys value as one key
and never splits it, so only the first key worked at best. The name
derived from the third whitespace-separated token could also end up
containing a newline, and the replace("\\n", "") calls stripped a literal
backslash-n rather than a newline and did nothing.

The builder now splits the content on line breaks and emits one "keys"
object and one "public_keys" entry per key. A key is named after its
comment when it has one no earlier key used, otherwise "key" for a lone
key and key0, key1, ... when there are several.
@wido
wido force-pushed the configdrive-multiple-ssh-keys branch from b157e67 to a08f069 Compare September 15, 2026 09:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant